﻿using System.Drawing;
using System.Runtime.InteropServices;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.DirectInput;
using SlimDX.Windows;
using System;
using Device = SlimDX.Direct3D9.Device;
using SwapChain = SlimDX.Direct3D9.SwapChain;
using System.Windows.Forms;

namespace SlimWater
{
    class SlimWaterApp : IDisposable
    {
        RenderForm form;

        Device device;

        Keyboard keyboard;
        Mouse mouse;

        Timer timer;
        Camera camera;

        ScreenQuad screen;

        Water water;
        Terrain terrain;
        Skybox sky;

        bool wireframeOn = false;

        public void Run()
        {
            InitializeForm();
            InitializeDirectXDevice();
            InitializeDirectXDeviceDefaults();
            InitializeInput();

            camera = new Camera(device);
            camera.Position(new Vector3(-10, 10, -10), Vector3.Zero);
            camera.SetProjection(1, 10000);

            screen = new ScreenQuad(device, form.ClientSize.Width, form.ClientSize.Height);

            water = new Water(device, 3 * 256.0f, form.ClientSize.Width, form.ClientSize.Height);
            terrain = new Terrain(device, 0.1f);
            sky = new Skybox(device, 300);

            timer = new Timer();

            MessagePump.Run(form, () =>
            {
                double dt = timer.GetTimeInterval();
                ReadKeyboard(100 * dt);
                ReadMouse();
                camera.SetView();
                sky.Update(camera.GetEP());

                PreRender();
                Render();  
                RenderScreenQuad();
            });            
        }

        private void PreRender()
        {
            water.RenderRefractionBegin(camera.GetEP());
            {
                RenderComponents();
            }
            water.RenderRefractionEnd();

            water.RenderReflectionBegin(camera.GetEP(), camera.GetLAP());
            {
                RenderComponents();
            }
            water.RenderReflectionEnd();
        }

        private void Render()
        {
            device.SetRenderState(RenderState.FillMode, wireframeOn ? FillMode.Wireframe : FillMode.Solid);

            screen.BeginScene();
            {
                device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);

                RenderComponents();

                water.Render(camera.GetEP(), timer.GetTime());
            }
            screen.EndScene();
        }

        private void RenderScreenQuad()
        {
            device.SetRenderState(RenderState.FillMode, FillMode.Solid);

            device.BeginScene();
            {
                device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);

                screen.Render(camera.GetEP(), timer.GetTime());
            }
            device.EndScene();
            device.Present();
        }

        private void RenderComponents()
        {
            terrain.Render();
            sky.Render();
        }

        #region ReadingInput
        private void ReadKeyboard(double dt)
        {
            KeyboardState keys = keyboard.GetCurrentState();

            if (keys.IsPressed(Key.LeftShift))
                dt *= 8;
            if (keys.IsPressed(Key.Escape))
                form.Close();
            if (keys.IsPressed(Key.W))
                camera.Move(dt);
            if (keys.IsPressed(Key.S))
                camera.Move(-dt);
            if (keys.IsPressed(Key.A))
                camera.Strafe(dt);
            if (keys.IsPressed(Key.D))
                camera.Strafe(-dt);
            if (keys.IsPressed(Key.Q))
                camera.MoveVert(dt);
            if (keys.IsPressed(Key.Z))
                camera.MoveVert(-dt);
            if (keys.IsPressed(Key.F1))
                wireframeOn = true;
            if (keys.IsPressed(Key.F2))
                wireframeOn = false;

            if (keys.IsPressed(Key.F9))
            {
                Texture.ToFile(water.reflectionTex, "reflection.png", ImageFileFormat.Png);
                Texture.ToFile(water.refractionTex, "refraction.png", ImageFileFormat.Png);
                Texture.ToFile(screen.texture, "original.png", ImageFileFormat.Png);
            }
        }

        public void ReadMouse()
        {
            mouse.Poll();

            MouseState state = mouse.GetCurrentState();
            bool[] buttonBuffer = state.GetButtons();

            if (buttonBuffer[0]) camera.MouseMove(state.X, state.Y);
        }
        #endregion ReadingInput

        #region InitializationMethods
        public void InitializeForm()
        {
            form = new RenderForm("SlimWater");
        }

        public void InitializeDirectXDevice()
        {
            device = new Device(new Direct3D(), 0, SlimDX.Direct3D9.DeviceType.Hardware, form.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters()
            {
                SwapEffect = SwapEffect.Discard,
                BackBufferFormat = Format.A8R8G8B8,
                BackBufferWidth = form.ClientSize.Width,
                BackBufferHeight = form.ClientSize.Height,
                AutoDepthStencilFormat = Format.D16,
                EnableAutoDepthStencil = true,
                PresentationInterval = PresentInterval.Immediate,
            });
        }

        public void InitializeDirectXDeviceDefaults()
        {
            device.SetSamplerState(0, SamplerState.MinFilter, TextureFilter.Linear);
            device.SetSamplerState(0, SamplerState.MagFilter, TextureFilter.Linear);
            device.SetSamplerState(0, SamplerState.MipFilter, TextureFilter.Linear);
            device.SetRenderState(RenderState.ZEnable, true);
            device.SetRenderState(RenderState.AlphaBlendEnable, true);
            device.SetRenderState(RenderState.PointSpriteEnable, true);
            device.SetRenderState(RenderState.PointScaleEnable, true);
            device.SetRenderState(RenderState.PointScaleA, 0);
            device.SetRenderState(RenderState.PointScaleB, 0);
            device.SetRenderState(RenderState.PointScaleC, 0.002f);

            Light light = new Light();
            light.Diffuse = Color.White;
            light.Ambient = Color.White;
            light.Direction = Vector3.Normalize(new Vector3(-1, -1, 0));
            light.Position = new Vector3(0, 50, 0);
            light.Type = LightType.Directional;
            device.SetLight(0, light);
            device.EnableLight(0, true);
        }

        public void InitializeInput()
        {
            DirectInput directInput = new DirectInput();

            keyboard = new Keyboard(directInput);
            keyboard.SetCooperativeLevel(form, CooperativeLevel.Nonexclusive | CooperativeLevel.Background);
            keyboard.Acquire();

            mouse = new SlimDX.DirectInput.Mouse(directInput);
            mouse.SetCooperativeLevel(form, CooperativeLevel.Nonexclusive | CooperativeLevel.Background);
            mouse.Properties.AxisMode = DeviceAxisMode.Relative;
            mouse.Acquire();
        }
        #endregion InitializationMethods

        public void Dispose()
        {
            water.Dispose();
            mouse.Dispose();
            keyboard.Dispose();

            foreach (var item in ObjectTable.Objects) item.Dispose();
        }
    }
}